import 'dart:async';

// ignore: depend_on_referenced_packages
import 'package:amap_flutter_base/amap_flutter_base.dart';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

import '../config/map_config.config.dart';
import '../models/map_location.model.dart';
import '../models/map_poi.model.dart';
import '../services/map_location_service.service.dart';
import '../services/map_service.service.dart';
import '../services/navigator_helper.service.dart';
import '../services/user_interaction.service.dart';
import './general_search_delegate.widget.dart';

class MapLocationSelector extends StatefulWidget {
  const MapLocationSelector(this.location, {super.key});

  final MapLocation location;

  @override
  State<MapLocationSelector> createState() => _MapLocationSelectorState();
}

class _MapLocationSelectorState extends State<MapLocationSelector> {
  // Map markers.
  final Map<String, Marker> _markers = <String, Marker>{};

  // For displaying bottom sheet for user interaction.
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  // For retrieving geo formatted address with coordinate.
  final MapService _mapService = MapService();

  // For locating user position.
  late final MapLocationService _mapLocationService =
      MapLocationService(_onLcationChanged);

  // For toast and bottom sheet usage.
  late final UserInteraction _userInteraction =
      UserInteraction(context: context);

  // Indicates if currently positioning.
  bool _isPositioning = false;

  // After tapping on map the LatLng will be stored.
  LatLng? _selectedLatLng;

  // Map controller for moving camera.
  AMapController? _mapController;

  // For displaying bottom sheet for user interaction.
  PersistentBottomSheetController? _bottomSheetController;

  @override
  void dispose() {
    _mapLocationService.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: _buildAppBar(),
      floatingActionButton: _buildFloatingActionButton(),
      body: _buildMap(),
    );
  }

  /// Builds app bar.
  AppBar _buildAppBar() {
    return AppBar(
      title: Text(_isPositioning ? '正在定位...' : '可点击地图选择'),
      actions: <Widget>[
        IconButton(
          icon: const Icon(Icons.search),
          onPressed: _query,
        ),
      ],
    );
  }

  /// Builds floating action button.
  Widget _buildFloatingActionButton() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        FloatingActionButton(
          child: const Icon(Icons.gps_fixed),
          onPressed: () {
            if (mounted) {
              setState(() => _isPositioning = true);
            }

            _mapLocationService.startLocation();
          },
        ),
        const SizedBox(height: 60.0),
      ],
    );
  }

  /// Builds map widget.
  Widget _buildMap() {
    return AMapWidget(
      apiKey: AMapApiKey(
          androidKey: MapConfig.androidKey, iosKey: MapConfig.iosKey),
      privacyStatement: const AMapPrivacyStatement(
          hasAgree: true, hasContains: true, hasShow: true),
      markers: Set<Marker>.of(_markers.values),
      myLocationStyleOptions: MyLocationStyleOptions(true),
      touchPoiEnabled: true,
      onMapCreated: _onMapCreated,
      onPoiTouched: (poi) => _showLocationInfo(poi.latLng!, name: poi.name!),
      onTap: _showLocationInfo,
    );
  }

  Widget _buildSearchResultItem(MapPoi poi) {
    return ListTile(
      leading: poi.photoUrls?.isEmpty == true
          ? const Icon(
              Icons.location_on,
              size: 30.0,
            )
          : AspectRatio(
              aspectRatio: 1.0,
              child: CachedNetworkImage(
                imageUrl: poi.photoUrls!.first,
                fit: BoxFit.cover,
              ),
            ),
      title: Text(poi.name!),
      subtitle: Text('${poi.city}${poi.address}'),
    );
  }

  Future<void> _query() async {
    final poi = await showSearch<dynamic>(
      context: context,
      delegate: GeneralSearchDelegate(
        _mapService.queryPoiByKeywords,
        buildResultItem: (entry) => _buildSearchResultItem(entry),
      ),
    );

    if (poi != null) {
      _onLcationChanged(poi.latitude, poi.longitude);
    }
  }

  /// Listens on location change event and moves camera.
  Future<void> _onLcationChanged(double? latitude, double? longitude) async {
    // Moves carmera to this coordinate.
    latitude ??= widget.location.latitude;
    longitude ??= widget.location.longitude;

    if (latitude != null && longitude != null) {
      final latLng = LatLng(latitude, longitude);

      // If there's a default input coordinate we need to set marker on it.
      _setMarker(latLng);

      _mapController!.moveCamera(CameraUpdate.newLatLng(latLng));

      await _showLocationInfo(latLng);

      if (mounted) {
        setState(() => _isPositioning = false);
      }
    }
  }

  /// Sets marker on map based on provided coordinate.
  void _setMarker(LatLng latLng) {
    final marker = Marker(position: latLng);

    if (mounted) {
      setState(() {
        _markers.clear();
        _markers[marker.id] = marker;
      });
    }
  }

  /// After map created we need to get map controller, set marker, move camera or positioning if needed.
  void _onMapCreated(AMapController controller) {
    // onMapCreated event will only be called once.
    // It's ok to put code here because it will not be called during re-build.
    _mapController = controller;

    if (widget.location.latitude != null && widget.location.longitude != null) {
      final latLng =
          LatLng(widget.location.latitude!, widget.location.longitude!);

      // If there's a default input coordinate we need to set marker on it.
      _setMarker(latLng);

      // If there's a default input coordinate we need to move camera to it.
      _mapController!.moveCamera(CameraUpdate.newLatLng(latLng));

      _showLocationInfo(latLng, name: widget.location.formattedAddress);
    } else {
      /// If no input coordinate we need to start positioning.
      if (mounted) {
        setState(() => _isPositioning = true);
      }

      _mapLocationService.startLocation();
    }
  }

  /// After getting location info we need to retrieve geo formatted address and display it on bottom sheet.
  ///
  /// [name] will be available for [onPoiTouched] event or [widget.location].
  Future<void> _showLocationInfo(LatLng latLng, {String? name}) async {
    // Sets the coordinate as marker.
    _setMarker(latLng);

    // This is for button tap event.
    _selectedLatLng = latLng;

    final location =
        await _mapService.queryByCoordinate(latLng.latitude, latLng.longitude);

    // Gets geo formatted address with coordinate if needed.
    widget.location.formattedAddress = (name ?? location?.formattedAddress)!;

    widget.location.address = location!.address;
    widget.location.province = location.province;
    widget.location.city = location.city;
    widget.location.district = location.district;
    widget.location.adCode = location.adCode;

    // Displays formatted address on bottom sheet.
    if (_bottomSheetController == null) {
      _bottomSheetController = _scaffoldKey.currentState?.showBottomSheet(
        (context) => _userInteraction.getSelectionBottomSheetContent([
          Expanded(child: Text(widget.location.formattedAddress!)),
          TextButton(
            child: const Text(
              '确定',
              style: TextStyle(color: Colors.blue),
            ),
            onPressed: () {
              // IMPORTANT!!!
              // Since we are not showing bottom sheet everytime that would cause we cannot access updated parameter [latLng].
              // That why [_selectedLatLng] introduced.
              widget.location.latitude = _selectedLatLng!.latitude;
              widget.location.longitude = _selectedLatLng!.longitude;

              _bottomSheetController!.close();
              NavigatorHelper.pop(context, widget.location);
            },
          ),
        ]),
      );

      // When bottom sheet is closed then we need to clear it.
      _bottomSheetController!.closed
          .whenComplete(() => _bottomSheetController = null);
    } else {
      // If bottom sheet already opens we only need to refresh its content.
      _bottomSheetController?.setState!(() {});
    }
  }
}
